# rePython - Python RPC server/client library
#
# Copyright (C) 2013 Andrew Dunai, andunai@gmail.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import socket
import netpoll
from threading import Thread
import inspect
import struct
import serializer
import errors
import security
import env
import util

env.loadEnv(globals(),
	('REPYTHON_PORT', env.UNSIGNED, 9999),
	('REPYTHON_TIMEOUT', env.UNSIGNED, 5)
)

class Server(Thread):
	def __init__(self):
		Thread.__init__(self)
		self.terminated = False
		self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.server.bind(('',REPYTHON_PORT))
		self.server.listen(5)
		self.namespaces = {}
#		self.daemon = True
		self.start()

	def run(self):
		while not self.terminated:
			sock, info = self.server.accept()
			RequestProcessor(self, sock, info)
		pass

	def provide(self, method, name = None, namespace='default'):
		if not hasattr(method, '__call__'):
			raise RPCNotCallableError('Object %s is not callable, thus cannot be provided' % method.__name__)
		methodName = name if name else method.__name__
		methodArgs = method.func_code.co_varnames
		if namespace not in self.namespaces.keys():
			self.namespaces[namespace] = {}
		self.namespaces[namespace][methodName] = (method, methodArgs)
		util.log.info('Registered method "%s" with args [%s]' % (methodName, ', '.join(methodArgs)))

class RequestProcessor(Thread):
	def __init__(self, server, sock, info):
		Thread.__init__(self)
		self.server = server
		self.sock = sock
		self.info = info
		self.rsaPool = security.RSAPool()
		self.daemon = True
		self.start()

	def disconnect(self):
		try:
			self.sock.close()
		except:
			pass
		return False

	def run(self):
		while True:
			lenData = netpoll.recv(self.sock, 4, REPYTHON_TIMEOUT)
			if not lenData:
				return self.disconnect()
			res = struct.unpack('<i', lenData)
			if not res:
				return self.disconnect()
			length = res[0]
			data = netpoll.recv(self.sock, length, REPYTHON_TIMEOUT)
			callData = serializer.unSerialize(data, self.rsaPool)

			util.log.debug(' * %s(%s)' % (callData['method'], ', '.join(str(x) for x in list(repr(x) for x in callData['kargs']) + ['%s=%s'%(str(x),repr(y)) for
			x,y in callData['kwargs'].items()])))

			if callData['namespace'] == 'default' and callData['method'] == 'announceEncryption' and len(callData['kargs']) == 2:
#				self.rsaPool = security.RSAPool()
				netpoll.send(self.sock, serializer.serializeResponse(myPublicKey = self.rsaPool.getMyPublic(), rsaPool = self.rsaPool))
				self.rsaPool.setTheirPublic(*callData['kargs'])
				continue

			if not (callData['namespace'] in self.server.namespaces.keys()):
				error = 'Cannot find namespace \'%s\'' % callData['namespace']
				util.log.exception(error)
				netpoll.send(self.sock, serializer.serializeResponse(exceptionValue = errors.RPCNamespaceError(error), rsaPool = self.rsaPool))
				continue
			if not (callData['method'] in self.server.namespaces[callData['namespace']].keys()):
				error = 'Cannot find method \'%s\' in namespace \'%s\'' % (callData['method'], callData['namespace'])
				util.log.exception(error)
				netpoll.send(self.sock, serializer.serializeResponse(exceptionValue = errors.RPCMethodError(error), rsaPool = self.rsaPool))
				continue
			method = self.server.namespaces[callData['namespace']][callData['method']]

			exceptionValue = None
			returnValue = None
			try:
				returnValue = method[0](*callData['kargs'], **callData['kwargs'])
			except Exception, e:
				util.log.exception('Exception in provided method: %s' % str(e))
				exceptionValue = e

			netpoll.send(self.sock, serializer.serializeResponse(exceptionValue = exceptionValue, returnValue = returnValue, rsaPool = self.rsaPool))
